#include "iuShell.hpp"
#include <cstdlib>
#include <cstdio>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

#define SC_CHECK(expr, msg) \
{ \
   if ((expr) == -1) \
   { \
      perror(msg); \
      exit(EXIT_FAILURE); \
   } \
}

/**
 * @brief Initialise le shell
 */
iuShell::iuShell()
{
   SC_CHECK(pipe(m_commandPipe), "pipe");
   SC_CHECK(pipe(m_resumePipe), "pipe");
}

/**
 * @brief Detruit le shell
 */
iuShell::~iuShell()
{
   close(m_commandPipe[0]);
   close(m_commandPipe[1]);
   close(m_resumePipe[0]);
   close(m_resumePipe[1]);
}

/**
 * @brief Execute le shell: un prompt va etre affiche et des qu'une commande
 * est entree par l'utilisateur, celle-ci est transmise sur le file-descriptor
 * de commandes (voir methodes getCommandFD() et getCommand()).
 * Le shell attend ensuite que l'utilisateur appelle la methode resume() pour
 * afficher a nouveau un prompt
 */
void iuShell::run()
{
   const char *prompt = "\033[32mIPoUDP\033[34m$\033[39m ";
   char *cmd;

   while ((cmd = readline(prompt)) != NULL)
   {
      executeCommand(cmd);
      free(cmd);
   }

   printf("\n");
}

/**
 * @brief Recupere le file-descriptor a surveiller en lecture pour savoir si
 * une commande a ete entree par l'utilisateur
 * @return Renvoie le file-descriptor servant a l'envoi des commandes
 */
int iuShell::getCommandFD()
{
   return m_commandPipe[0];
}

/**
 * @brief Recupere la commande entree par l'utilisateur et la scinde en arguments
 * @param args le tableau ou passer les arguments de la commande
 * @return Renvoie le nombre d'arguments de la commande
 * @note La commande renvoyee devra etre supprimee avec la methode freeCommand()
 */
int iuShell::getCommand(char *args[MAX_ARGS])
{
   char *buffer, *cmd, *arg;
   int len, readLen, l, i;

   if (!args)
      return 0;

   //On lit la taille de la commande
   SC_CHECK(read(m_commandPipe[0], &len, sizeof(int)), "[Shell] Unable to read the length of the incoming command");
   buffer = (char *)malloc(len);

   //On lit la commande
   l = 0;
   while (len - l > 0)
   {
      SC_CHECK((readLen = read(m_commandPipe[0], buffer + l, len - l)), "[Shell] Unable to read the incoming command");
      l += readLen;
   }

   //On scinde la commande en arguments
   cmd = buffer;
   i = 0;
   while ((i < MAX_ARGS) && ((arg = strtok(cmd, " \t")) != NULL))
   {
      args[i] = strdup(arg);
      cmd = NULL;
      i++;
   }

   free(buffer);
   return i;
}

/**
 * @brief Libere une commande recuperee avec la methode getCommand()
 * @param numArgs le nombre d'arguments de la commande
 * @param args les arguments de la commande a liberer
 */
void iuShell::freeCommand(int numArgs, char *args[MAX_ARGS]) const
{
   //On libere chaque argument de la commande
   for (int i = 0; i < numArgs; i++)
   {
      free(args[i]);
   }
}

/**
 * @brief Execute la commande lue dans le buffer
 * @param buffer le buffer contenant la commande
*/
void iuShell::executeCommand(const char *buffer)
{
   int len, writeLen, l;
   char c;

   if (!buffer || *buffer == '\0')
      return;

   //Ajout dans l'historique
   add_history(buffer);

   //On ecrit la longueur de la commande dans le pipe
   len = strlen(buffer) + 1;
   SC_CHECK(write(m_commandPipe[1], &len, sizeof(int)), "[Shell] Unable to write the command's length");

   //On ecrit la commande dans le pipe
   l = 0;
   while (len - l > 0)
   {
      SC_CHECK((writeLen = write(m_commandPipe[1], buffer + l, len - l)), "[Shell] Unable to write the command");
      l += writeLen;
   }

   //Attend que la commande soit executee
   SC_CHECK(read(m_resumePipe[0], &c, 1), "[Shell] Unable to wait for the 'resume' order");
}

/**
 * @brief Fait reprendre l'affichage du prompt dans le shell.
 * Cette methode devra etre appelee des que le traitement d'une commande est fini
 */
void iuShell::resume()
{
   SC_CHECK(write(m_resumePipe[1], "", 1), "[Shell] Unable to write the 'resume' order");
}
